package com.example.ojcodesandbox.utils;

import cn.hutool.core.date.StopWatch;
import cn.hutool.core.io.FileUtil;
import com.example.ojcodesandbox.config.DockerConfig;
import com.example.ojcommon.common.ErrorCode;
import com.example.ojcommon.common.ExecuteCodeResponse;
import com.example.ojcommon.enums.LanguageEnum;
import com.example.ojcommon.exception.BusinessException;
import com.example.ojcommon.common.JudgeInfo;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.StatsCmd;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.command.ExecStartResultCallback;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 用户源文件文件工具类
 */
public class CodeUtils {

    public static final String GLOBAL_CODE_DIR_NAME = "tempCode";

    public static final String GLOBAL_CODE_NAME = "Main";

    public static final long TIME_OUT = 3000L;

    /**
     * 创建用户代码文件
     *
     * @param code
     * @return
     */
    public static File createFile(String code, Integer language) {
        String suffix = LanguageEnum.valueOf(language).getSuffix();
//        String suffix = "";
        // 当前项目路径
        String globalCodePath = System.getProperty("user.dir") + File.separator + GLOBAL_CODE_DIR_NAME;
        // 判断全局代码目录是否存在，不存在就创建
        if (!FileUtil.exist(globalCodePath)) {
            FileUtil.mkdir(globalCodePath);
        }
        // 存放用户代码目录路径
        String codeParentPath = globalCodePath + File.separator + UUID.randomUUID();
        // 用户代码路径
        String userCodePath = codeParentPath + File.separator + GLOBAL_CODE_NAME + suffix;
        // 用户代码文件
        return FileUtil.writeString(code, userCodePath, StandardCharsets.UTF_8);
    }

    /**
     * 编译命令，将java文件编译成.class文件
     * 这个方法只负责编译，只要编译失败就抛出异常
     *
     * @param command 编译指令
     */
    public static void compileProcess(String command) throws Exception {
        Process process = Runtime.getRuntime().exec(command);
        int i = process.waitFor();
        if (i != 0) {
            throw new Exception("编译失败");
        }
    }

    public static ExecuteCodeResponse runFile(String codeParentPath, List<String> inputList, String images, String[] command) {
        //获取dockerClient
        DockerClient dockerClient = DockerConfig.createDockerClientConfig();
        //创建容器时，指定文件映射路径，把本地文件同步到容器中，可以让容器访问
        HostConfig hostConfig = new HostConfig();
        hostConfig.setBinds(new Bind(codeParentPath, new Volume("/app")));
        hostConfig.withMemory(1000 * 1000 * 100L);//设置容器的最大内存
        hostConfig.withCpuCount(1L);//设置容器可以占用CPU的个数
        //创建一个容器，这个容器是可交互的，可以接收多个测试用例并执行
        CreateContainerResponse createContainerResponse = dockerClient.createContainerCmd(images)
            .withHostConfig(hostConfig)
            .withAttachStdin(true)//可以向容器输入测试用例
            .withAttachStderr(true)//可以从容器获取错误
            .withAttachStdout(true)//可以从容器获取输出
            .withTty(true)//设置为可交互
            .exec();
        //获取容器id
        String id = createContainerResponse.getId();
        //启动容器
        dockerClient.startContainerCmd(id).exec();
        // 命令和参数
        ExecuteCodeResponse executeCodeResponse = new ExecuteCodeResponse();
        List<String> resultList = new ArrayList<>();
        JudgeInfo judgeInfo = new JudgeInfo();
        long time = 0L;
        final long[] maxMemory = {0L};
        try {
            for (String input : inputList) {
                //定义定时器
                StopWatch stopWatch = new StopWatch();
                ExecCreateCmdResponse execCreateCmdResponse = dockerClient.execCreateCmd(id)
                    .withCmd(command)
                    .withAttachStderr(true)
                    .withAttachStdin(true)
                    .withAttachStdout(true)
                    .exec();
                StringBuilder sb = new StringBuilder();
                String execId = execCreateCmdResponse.getId();
                ExecStartResultCallback execStartResultCallback = new ExecStartResultCallback() {
                    @Override
                    public void onNext(Frame frame) {
                        sb.append(new String(frame.getPayload()));
                        super.onNext(frame);
                    }
                };
                // 获取占用的内存
                StatsCmd statsCmd = dockerClient.statsCmd(id);
                ResultCallback<Statistics> statisticsResultCallback = statsCmd.exec(new ResultCallback<Statistics>() {

                    @Override
                    public void onNext(Statistics statistics) {
                        if (statistics.getMemoryStats().getUsage() != null)
                            maxMemory[0] = Math.max(statistics.getMemoryStats().getUsage(), maxMemory[0]);
                    }

                    @Override
                    public void close() throws IOException {
                    }

                    @Override
                    public void onStart(Closeable closeable) {
                    }

                    @Override
                    public void onError(Throwable throwable) {
                    }

                    @Override
                    public void onComplete() {
                    }
                });
                statsCmd.exec(statisticsResultCallback);

                try {
                    String inputAry = input + "\n";
                    stopWatch.start();//开始时间
                    dockerClient.execStartCmd(execId)
                        .withStdIn(new ByteArrayInputStream(inputAry.getBytes()))//输入用例
                        .exec(execStartResultCallback)
                        .awaitCompletion(TIME_OUT, TimeUnit.MILLISECONDS);
                    stopWatch.stop();//结束时间
                    time = Math.max(stopWatch.getLastTaskTimeMillis(), time);//计算所用时间
                    statsCmd.close();
                } catch (InterruptedException e) {
                    throw new BusinessException(ErrorCode.CODE_RUNNING_ERROR);
                }
                resultList.add(sb.toString());
            }
            judgeInfo.setMemory(maxMemory[0] / 1000 / 1000);
            judgeInfo.setTime(time);
            judgeInfo.setResultList(resultList);
            executeCodeResponse.setJudgeInfo(judgeInfo);
        } catch (Exception e) {
            throw new BusinessException(ErrorCode.CODE_RUNNING_ERROR);
        } finally {
            // 停止容器
            dockerClient.stopContainerCmd(id).exec();
            // 删除容器
            dockerClient.removeContainerCmd(id).exec();
        }
        return executeCodeResponse;
    }

    /**
     * 清理本地文件
     *
     * @param codeParentFile
     * @return
     */
    public static boolean deleteFile(File codeParentFile) {
        //清理生成的代码文件
        if (codeParentFile != null) {
            return FileUtil.del(codeParentFile.getAbsolutePath());
        }
        return false;
    }
}
